#include <sxPlotAxes_Archetype.h>
#include <sxPlotAxes_Representation.h>

using Sx::Graphics::Objects::PlotAxes;
using Sx::Graphics::Representations::Objects::PlotAxes_Representation;

Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::PlotAxes_Archetype(const std::shared_ptr<RendererImplementation>& renderer) : 
	GeometricObject_Archetype(std::make_shared<PlotAxes>(), renderer, std::make_shared<PlotAxes_Representation>()) {
		
}

Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::PlotAxes_Archetype(const std::shared_ptr<RendererImplementation>& renderer, unsigned int nxTicks, unsigned int pxTicks, unsigned int nyTicks, unsigned int pyTicks, unsigned int nzTicks, unsigned int pzTicks, float tickSpacing) : 
	GeometricObject_Archetype(std::make_shared<PlotAxes>(nxTicks, pxTicks, nyTicks, pyTicks, nzTicks, pzTicks, tickSpacing), renderer, std::make_shared<PlotAxes_Representation>()) {
	
}

Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::~PlotAxes_Archetype() {}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::construct() {
	if ( this->object == nullptr ) {
		std::cout << "[PlotAxes_Archetype:construct] Error: PlotAxes has not been initialized (nullptr)." << std::endl;
		std::cout << "  PlotAxes_Archetype not constructed." << std::endl;
		return false;
	}

	if ( this->physicalRepresentation == nullptr ) {
		std::cout << "[PlotAxes_Archetype:construct] Error: PlotAxes physical representation has not been initialized (nullptr)." << std::endl;
		std::cout << "  PlotAxes_Archetype not constructed." << std::endl;
		return false;
	}

	//--------------------------------------------------------------------------
	// Construct the physical representation of the plot axes from the abstract
	// object representation.
	//--------------------------------------------------------------------------
	return  this->constructUnitPlotAxes();
}

void Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::update(float dt) const { return; }
bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::pick(const Eigen::Vector3f& pickSegmentStart, const Eigen::Vector3f& pickSegmentEnd, Eigen::Vector3f& intersectionPoint) { return false; }
bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::intersect(const Eigen::Vector3f& segment_p0, const Eigen::Vector3f& segment_p1, Eigen::Vector3f& intersectionPoint) { return false; }

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setXTicks(unsigned int ticks, bool positive) {
	if ( nullptr == this->object ) {
		std::cerr << "[PlotAxes_Archetype:setXTicks] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}

	this->object->setXTicks(ticks, positive);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setYTicks(unsigned int ticks, bool positive) {
	if ( nullptr == this->object ) {
		std::cerr << "[PlotAxes_Archetype:setYTicks] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}

	this->object->setYTicks(ticks, positive);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setZTicks(unsigned int ticks, bool positive) {
	if ( nullptr == this->object ) {
		std::cerr << "[PlotAxes_Archetype:setZTicks] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}

	this->object->setZTicks(ticks, positive);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setGridTicks(unsigned int nxTicks, unsigned int pxTicks, unsigned int nyTicks, unsigned int pyTicks, unsigned int nzTicks, unsigned int pzTicks) {
	if ( nullptr == this->object ) {
		std::cerr << "[PlotAxes_Archetype:setPlotTicks] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}

	this->object->setXTicks(nxTicks, false);
	this->object->setXTicks(pxTicks, true);
	this->object->setYTicks(nyTicks, false);
	this->object->setYTicks(pyTicks, true);
	this->object->setZTicks(nzTicks, false);
	this->object->setZTicks(pzTicks, true);
	return this->construct();
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setDisplayX(bool b) {
	if ( nullptr == this->object ) {
		std::cerr << "[PlotAxes_Archetype:setDisplayX] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}

	this->object->setDisplayX(b);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setDisplayY(bool b) {
	if ( this->object == nullptr ) {
		std::cerr << "[PlotAxes_Archetype:setDisplayY] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}
	
	this->object->setDisplayY(b);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setDisplayZ(bool b) {
	if ( this->object == nullptr ) {
		std::cerr << "[PlotAxes_Archetype:setDisplayZ] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}
	
	this->object->setDisplayZ(b);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setDisplayTicks(bool b) {
	if ( this->object == nullptr ) {
		std::cerr << "[PlotAxes_Archetype:setDisplayTicks] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}
	
	this->object->setDisplayTicks(b);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setDisplayTickLabels(bool b) {
	if ( this->object == nullptr ) {
		std::cerr << "[PlotAxes_Archetype:setDisplayTickLabels] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}
	
	this->object->setDisplayTickLabels(b);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setDisplayGrid(bool b) {
	if ( this->object == nullptr ) {
		std::cerr << "[PlotAxes_Archetype:setDisplayGrid] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}
	
	this->object->setDisplayGrid(b);
	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::setDisplayGrid(Math::AxisPlane plane, bool b) {
	if ( this->object == nullptr ) {
		std::cerr << "[PlotAxes_Archetype:setDisplayGrid] Error: Object representation has not been initialized (nullptr)." << std::endl;
		return false;
	}
	
	this->object->setDisplayGrid(plane, b);
	return true;
}

void GenerateUnit_GridLine(Sx::Graphics::Util::VectorArray& vertices, float uPosition, float negVPosition, float posVPosition, Sx::Math::AxisPlane plane, bool primary) {
	float uP, uS, vP, vS;

	if ( primary == true ) {
		uP = uS = uPosition;
		vP = negVPosition;
		vS = posVPosition;
	}
	else {
		uP = negVPosition;
		uS = posVPosition;
		vP = vS = uPosition;
	}

	switch ( plane ) {
	case Sx::Math::XY:
		vertices.addLast(Eigen::Vector3f(uP, vP, 0.0f));
		vertices.addLast(Eigen::Vector3f(uS, vS, 0.0f));
	break;
	case Sx::Math::YZ:
		vertices.addLast(Eigen::Vector3f(0.0f, uP, vP));
		vertices.addLast(Eigen::Vector3f(0.0f, uS, vS));
	break;
	case Sx::Math::XZ:
		vertices.addLast(Eigen::Vector3f(uP, 0.0f, vP));
		vertices.addLast(Eigen::Vector3f(uS, 0.0f, vS));
	break;
	};
}

void GenerateUnit_GridLines(Sx::Graphics::Util::VectorArray& vertices, unsigned int tickCount, float uPosition, float negVPosition, float posVPosition, float spacing, Sx::Math::AxisPlane plane, bool primary, bool positive) {
	for ( unsigned int i = 0; i < tickCount; i++ ) {
		GenerateUnit_GridLine(vertices, uPosition, negVPosition, posVPosition, plane, primary);
		if ( positive ) uPosition += spacing;
		else uPosition -= spacing;
	}
}

void GenerateUnit_Grid(Sx::Graphics::Util::VectorArray& vertices, Sx::Math::AxisPlane plane, unsigned int posUTicks, unsigned int negUTicks, unsigned int posVTicks, unsigned int negVTicks, float spacing) {
	float uStart = -std::abs(spacing);
	float vStart = -std::abs(spacing);
	float negUPosition = -static_cast<float>(negUTicks * spacing);
	float posUPosition = posUTicks * spacing;
	float negVPosition = -static_cast<float>(negVTicks * spacing);
	float posVPosition = posVTicks * spacing;

	//--------------------------------------------------------------------------
	// Generate all vertical lines across the U axis
	//--------------------------------------------------------------------------
	GenerateUnit_GridLines(vertices, negUTicks, uStart, negVPosition, posVPosition, spacing, plane, true, false);
	uStart = std::abs(spacing);
	GenerateUnit_GridLines(vertices, posUTicks, uStart, negVPosition, posVPosition, spacing, plane, true, true);

	//--------------------------------------------------------------------------
	// Generate all horizontal lines across the V axis
	//--------------------------------------------------------------------------
	GenerateUnit_GridLines(vertices, negVTicks, vStart, negUPosition, posUPosition, spacing, plane, false, false);
	vStart = std::abs(spacing);
	GenerateUnit_GridLines(vertices, posVTicks, vStart, negUPosition, posUPosition, spacing, plane, false, true);
}

bool GenerateUnit_GridTicks(Sx::Graphics::Util::VectorArray& vertices, Sx::Math::Axis axis, unsigned int ticks, float spacing, float tickSize) {
	if ( ticks == 0 ) return true;

	float curPosition = 0.0f;
	int coef = 1;

	if ( axis == Sx::Math::NX || axis == Sx::Math::NY || axis == Sx::Math::NZ ) coef *= -1;
	for ( unsigned int i = 0; i <= ticks; i++ ) {
		switch ( axis ) {
		case Sx::Math::X:
		case Sx::Math::NX:
			vertices.addLast(Eigen::Vector3f(curPosition, -tickSize, 0.0f));
			vertices.addLast(Eigen::Vector3f(curPosition, tickSize, 0.0f));
		break;
		case Sx::Math::Y:
		case Sx::Math::NY:
			vertices.addLast(Eigen::Vector3f(-tickSize, curPosition, 0.0f));
			vertices.addLast(Eigen::Vector3f(tickSize, curPosition, 0.0f));
		break;
		case Sx::Math::Z:
		case Sx::Math::NZ:
			vertices.addLast(Eigen::Vector3f(-tickSize, 0.0f, curPosition));
			vertices.addLast(Eigen::Vector3f(tickSize, 0.0f, curPosition));
		break;
		};
		curPosition += (coef * spacing);
	}

	return true;
}

bool Sx::Graphics::Archetypes::Objects::PlotAxes_Archetype::constructUnitPlotAxes() {
	if ( this->object == nullptr ) return false;
	if ( this->physicalRepresentation == nullptr ) return false;

	std::shared_ptr<PlotAxes> & plot = this->object;

	this->physicalRepresentation->nx = Eigen::Vector3f(-(plot->getXTicks(false) * plot->getTickSpacing()), 0.0f, 0.0f);
	this->physicalRepresentation->px = Eigen::Vector3f(plot->getXTicks(true) * plot->getTickSpacing(), 0.0f, 0.0f);
	this->physicalRepresentation->ny = Eigen::Vector3f(0.0f, -(plot->getYTicks(false) * plot->getTickSpacing()), 0.0f);
	this->physicalRepresentation->py = Eigen::Vector3f(0.0f, plot->getYTicks(true) * plot->getTickSpacing(), 0.0f);
	this->physicalRepresentation->nz = Eigen::Vector3f(0.0f, 0.0f, -(plot->getZTicks(false) * plot->getTickSpacing()));
	this->physicalRepresentation->pz = Eigen::Vector3f(0.0f, 0.0f, plot->getZTicks(true) * plot->getTickSpacing());

	this->physicalRepresentation->vertices.clear();
	this->physicalRepresentation->grid_XY.clear();
	this->physicalRepresentation->grid_XZ.clear();
	this->physicalRepresentation->grid_YZ.clear();

	//--------------------------------------------------------------------------
	// Generate a grid for each axis plane
	//--------------------------------------------------------------------------
	unsigned int nxTicks = plot->getXTicks(false);
	unsigned int pxTicks = plot->getXTicks(true);
	unsigned int nyTicks = plot->getYTicks(false);
	unsigned int pyTicks = plot->getYTicks(true);
	unsigned int nzTicks = plot->getZTicks(false);
	unsigned int pzTicks = plot->getZTicks(true);
	float spacing = plot->getTickSpacing();

	GenerateUnit_Grid(physicalRepresentation->grid_XY, Math::XY, pxTicks, nxTicks, pyTicks, nyTicks, spacing);
	GenerateUnit_Grid(physicalRepresentation->grid_YZ, Math::YZ, pyTicks, nyTicks, pzTicks, nzTicks, spacing);
	GenerateUnit_Grid(physicalRepresentation->grid_XZ, Math::XZ, pxTicks, nxTicks, pzTicks, nzTicks, spacing);

	//--------------------------------------------------------------------------
	// Generate the line ticks for each of the axes
	//--------------------------------------------------------------------------
	float tickSize = 0.1f;
	GenerateUnit_GridTicks(physicalRepresentation->xTickLines, Math::NX, nxTicks, spacing, tickSize);
	GenerateUnit_GridTicks(physicalRepresentation->xTickLines, Math::X, pxTicks, spacing, tickSize);
	GenerateUnit_GridTicks(physicalRepresentation->yTickLines, Math::NY, nyTicks, spacing, tickSize);
	GenerateUnit_GridTicks(physicalRepresentation->yTickLines, Math::Y, pyTicks, spacing, tickSize);
	GenerateUnit_GridTicks(physicalRepresentation->zTickLines, Math::NZ, nzTicks, spacing, tickSize);
	GenerateUnit_GridTicks(physicalRepresentation->zTickLines, Math::Z, pzTicks, spacing, tickSize);
	return true;
}

